package com.gitee.jdbc.elasticsearch;

import io.searchbox.client.JestClientFactory;
import io.searchbox.client.config.HttpClientConfig;
import io.searchbox.client.config.HttpClientConfig.Builder;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpHost;

public class ElasticsearchConnection implements Connection {

  private String host;
  private int port;
  private Properties props;
  private ElasticsearchClient client;
  private boolean isClosed = false;
  private int timeout = Integer.MAX_VALUE;

  public ElasticsearchConnection(String host, int port, Properties props) throws SQLException {
    this.host = host;
    this.port = port;
    this.props = props;
    this.client = buildClient();
  }

  private ElasticsearchClient buildClient() throws SQLException {
    String prefix = Boolean.parseBoolean(
        this.props.getProperty("useHttps", "false")
    ) ? "https://" : "http://";
    try {
      JestClientFactory factory = new JestClientFactory();
      Builder httpClientConfig = new HttpClientConfig
          .Builder(prefix + this.host + ":" + this.port)
          .setPreemptiveAuth(new HttpHost(prefix + this.host + ":" + this.port))
          .multiThreaded(true)
          .connTimeout(30000)
          .readTimeout(60000)
          .maxTotalConnection(200)
          .requestCompressionEnabled(false)
          .discoveryEnabled(false)
          .discoveryFrequency(5l, TimeUnit.MINUTES);
      String user = this.props.getProperty("user");
      String password = this.props.getProperty("password");
      if (!("".equals(user) || "".equals(password))) {
        httpClientConfig.defaultCredentials(user, password);
      }
      factory.setHttpClientConfig(httpClientConfig.build());
      return new ElasticsearchClient(factory.getObject());
    } catch (Throwable t) {
      throw new SQLException("Unable to connect to database", t);
    }
  }

  private void checkClientClose() throws SQLException {
    if (isClosed) {
      throw new SQLException("Unable to connect");
    }
  }

  public ElasticsearchClient getClient() {
    return client;
  }

  @Override
  public <T> T unwrap(Class<T> iface) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public Statement createStatement() throws SQLException {
    checkClientClose();
    return new ElasticsearchStatement(this);
  }

  @Override
  public PreparedStatement prepareStatement(String sql) throws SQLException {
    checkClientClose();
    return new ElasticsearchPreparedStatement(this, sql);
  }

  @Override
  public CallableStatement prepareCall(String sql) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
    checkClientClose();
    return new ElasticsearchStatement(this);
  }

  @Override
  public String nativeSQL(String sql) throws SQLException {
    return sql;
  }

  @Override
  public void setAutoCommit(boolean autoCommit) throws SQLException {
  }

  @Override
  public boolean getAutoCommit() throws SQLException {
    return true;
  }

  @Override
  public void commit() throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public void rollback() throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public void close() throws SQLException {
    client.close();
    client = null;
    isClosed = true;
  }

  @Override
  public boolean isClosed() throws SQLException {
    return isClosed;
  }

  @Override
  public DatabaseMetaData getMetaData() throws SQLException {
    return new ElasticsearchDatabaseMetaData(host, port, client, props, this);
  }

  @Override
  public void setReadOnly(boolean readOnly) throws SQLException {
  }

  @Override
  public boolean isReadOnly() throws SQLException {
    return false;
  }

  @Override
  public void setCatalog(String catalog) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public String getCatalog() throws SQLException {
    return null;
  }

  @Override
  public void setTransactionIsolation(int level) throws SQLException {
  }

  @Override
  public int getTransactionIsolation() throws SQLException {
    return Connection.TRANSACTION_NONE;
  }

  @Override
  public SQLWarning getWarnings() throws SQLException {
    return null;
  }

  @Override
  public void clearWarnings() throws SQLException {
  }

  @Override
  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
      throws SQLException {
    checkClientClose();
    return new ElasticsearchPreparedStatement(this, sql);
  }

  @Override
  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public Map<String, Class<?>> getTypeMap() throws SQLException {
    return new HashMap<>();
  }

  @Override
  public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public void setHoldability(int holdability) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public int getHoldability() throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public Savepoint setSavepoint() throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public Savepoint setSavepoint(String name) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public void rollback(Savepoint savepoint) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public void releaseSavepoint(Savepoint savepoint) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
      throws SQLException {
    checkClientClose();
    return new ElasticsearchStatement(this);
  }

  @Override
  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
      int resultSetHoldability) throws SQLException {
    checkClientClose();
    return new ElasticsearchPreparedStatement(this, sql);
  }

  @Override
  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
      int resultSetHoldability) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
    checkClientClose();
    return new ElasticsearchPreparedStatement(this, sql);
  }

  @Override
  public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
    checkClientClose();
    return new ElasticsearchPreparedStatement(this, sql);
  }

  @Override
  public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
    checkClientClose();
    return new ElasticsearchPreparedStatement(this, sql);
  }

  @Override
  public Clob createClob() throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public Blob createBlob() throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public NClob createNClob() throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public SQLXML createSQLXML() throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public boolean isValid(int timeout) throws SQLException {
    return true;
  }

  @Override
  public void setClientInfo(String name, String value) throws SQLClientInfoException {
    props.setProperty(name, value);
  }

  @Override
  public void setClientInfo(Properties properties) throws SQLClientInfoException {
    this.props = properties;
  }

  @Override
  public String getClientInfo(String name) throws SQLException {
    return props.getProperty(name);
  }

  @Override
  public Properties getClientInfo() throws SQLException {
    return this.props;
  }

  @Override
  public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
    return null;
  }

  @Override
  public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public void setSchema(String schema) throws SQLException {
  }

  @Override
  public String getSchema() throws SQLException {
    return "com/gitee/jdbc/elasticsearch";
  }

  @Override
  public void abort(Executor executor) throws SQLException {
    throw new SQLFeatureNotSupportedException(Constants.getLoggingInfo());
  }

  @Override
  public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
    this.timeout = milliseconds;
  }

  @Override
  public int getNetworkTimeout() throws SQLException {
    return timeout;
  }
}
